import random
import os
import paho.mqtt.client as mqtt
import json
from typing import Dict, List, Optional
import time
import threading



broker = 'rc1158fd.ala.cn-hangzhou.emqxsl.cn'
broker = 'i10a009e.ala.dedicated.aliyun.emqxcloud.cn'
broker = '118.31.104.242'
username = 'fly'
password = '123456'
port = 8883
port = 1883
# 如果 broker 需要鉴权，设置用户名密码
username = 'fly'
password = '123456'
client_id = f'python-mqtt-{random.randint(0, 1000)}'


class MQTTClient:
    _instance = None
    
    def __new__(cls,local_host_mode:bool = False):
        if cls._instance is None:
            cls._instance = super(MQTTClient, cls).__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    
    def __init__(self,local_host_mode:bool = False):
        if self._initialized:
            return
        #mqtt link local host flag
        self.local_host_mode = local_host_mode          
        # 初始化属性
        if self.local_host_mode :
            self.broker = 'localhost'
            self.port = 1883
        else:
            self.broker: str = broker
            self.port: int = port
        self.client = mqtt.Client()
        if self.local_host_mode != True:
        # # 获取当前脚本所在的目录
        #     current_dir = os.path.dirname(os.path.abspath(__file__))
        #     # 拼接文件的完整路径
        #     file_path = os.path.join(current_dir, 'emqxcloud-ca.crt')
        #     self.client.tls_set(ca_certs=file_path)
            self.client.username_pw_set(username, password)
        self._initialized = True

        self.message_handle_callback = None
        self.connect_handle_callback = None
        self.message_handle_callback_list = []
        self.connect_handle_callback_list = []
        
        # 设置回调函数
        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        self.client.on_disconnect = self._on_disconnect  
        # self.client.on_subscribe = self._on_subscribe
        
        # 自动重连相关参数
        self.reconnect_delay = 1  # 初始重连延迟(秒)
        self.max_reconnect_delay = 60  # 最大重连延迟(秒)
        self.should_reconnect = True  # 是否应该重连的标志
        self.topics = []  # 存储已订阅的主题
        self.rmesAgreement = []  # 存储协议主题
        self.connecting = False  # 连接状态标志

    # def _on_subscribe(self, client, userdata, mid, granted_qos):
    #     """订阅回调函数"""
    #     print(f"已成功订阅主题: {self.topics[mid-1]},qos{granted_qos}")
        
    def add_message_handle_callback(self,message_handle):
        self.message_handle_callback_list.append(message_handle)
    
    def add_connect_handle_callback(self,connect_handle):
        self.connect_handle_callback_list.append(connect_handle)

    def set_message_handle_callback(self,message_handle):
        self.message_handle_callback = message_handle

    def set_connect_handle_callback(self,connect_handle):
        self.connect_handle_callback = connect_handle

    def _on_connect(self, client, userdata, flags, rc):
        """连接回调函数"""
        if rc == 0:
            print(f"已成功连接到MQTT服务器 {self.broker}")
            # 重新订阅所有主题
            topics = [item["topic"] for item in self.rmesAgreement]
            for topic in topics:
                self.client.subscribe(topic)
            for topic in self.topics:
                self.client.subscribe(topic)
            # 重置重连延迟
            self.reconnect_delay = 1
            self.connecting = False
            if self.connect_handle_callback is not None:
                self.connect_handle_callback(client, userdata, flags, rc)
            for connect_handle_callback in self.connect_handle_callback_list:
                connect_handle_callback(client, userdata, flags, rc)
        else:
            print(f"连接失败，返回码: {rc}")
            
    def _on_message(self, client, userdata, msg):
        """消息接收回调函数"""
        if self.message_handle_callback is not None:
            self.message_handle_callback(client, userdata, msg)
        for message_handle_callback in self.message_handle_callback_list:
            message_handle_callback(client, userdata, msg)

    def _connect_thread(self):
        """在后台线程中执行连接操作"""
        while self.should_reconnect and not self.is_connected():
            try:
                print(f"尝试连接到MQTT服务器 {self.broker}...")
                self.client.connect(self.broker, self.port, 60)
                self.client.loop_start()
                return True
            except Exception as e:
                print(f"连接失败: {e}")
                print(f"将在 {self.reconnect_delay} 秒后重试...")
                time.sleep(self.reconnect_delay)
                # 使用指数退避策略增加重试间隔
                self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)
            
    def connect(self) -> bool:
        """非阻塞地连接到MQTT服务器"""
        if self.connecting:
            print("已有连接任务在进行中")
            return False
            
        self.connecting = True
        self.should_reconnect = True
        # 在新线程中执行连接操作
        threading.Thread(target=self._connect_thread, daemon=True).start()
        return True
            
    def disconnect(self):
        """断开与MQTT服务器的连接"""
        self.should_reconnect = False  # 禁用自动重连
        self.connecting = False
        self.client.loop_stop()
        self.client.disconnect()
        
    def is_connected(self) -> bool:
        """检查是否已连接到MQTT服务器
        
        Returns:
            bool: 如果已连接返回True，否则返回False
        """
        return self.client.is_connected()
        
    def publish_message(self, topic: str, data: Dict) -> bool:
        """发布消息到指定主题"""
        try:
            message = json.dumps(data)
            result = self.client.publish(topic, message)
            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                #print(f"成功发布消息到主题 {topic}: {message}")
                return True
            else:
                print(f"发布消息失败: {result.rc}")
                return False
        except Exception as e:
            print(f"发布消息时发生错误: {e}")
            return False
            
    def subscribe_topics(self, topics: List[str]):
        """订阅多个主题"""
        for topic in topics:
            if topic not in self.topics:
                self.topics.append(topic)
                if self.client.is_connected():
                    self.client.subscribe(topic)
                    print(f"已订阅主题: {topic}")
                    
    def unsubscribe_topics(self, topics: List[str]):
        """取消订阅多个主题"""
        for topic in topics:
            if topic in self.topics:
                self.topics.remove(topic)
                if self.client.is_connected():
                    self.client.unsubscribe(topic)
                    print(f"已取消订阅主题: {topic}")

    def _reconnect_thread(self):
        """在后台线程中执行重连操作"""
        try:
            if not self.connecting:
                print("尝试重新连接到MQTT服务器...")
                self.client.reconnect()
                return True
        except Exception as e:
            print(f"重连失败: {e}")
            return False

    def reconnect(self) -> bool:
        """非阻塞地执行断线重连"""
        if self.is_connected():
            print("已经处于连接状态，无需重连")
            return True
            
        if self.connecting:
            print("已有连接任务在进行中")
            return False
            
        # 在新线程中执行重连操作
        threading.Thread(target=self._reconnect_thread, daemon=True).start()
        return True
            
    def _on_disconnect(self, client, userdata, rc):
        """断开连接的回调函数"""
        if rc != 0:
            print(f"意外断开连接，返回码: {rc}")
            if self.should_reconnect and not self.connecting:
                # 在新线程中执行自动重连             
                def auto_reconnect_task():
                    while self.should_reconnect and not self.is_connected():
                        try:
                            print(f"尝试在 {self.reconnect_delay} 秒后重新连接...")
                            time.sleep(self.reconnect_delay)
                            if not self.connecting:
                                self.client.reconnect()
                            # 如果重连失败，增加延迟时间
                            self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)
                        except Exception as e:
                            print(f"重连失败: {e}")
                
                # 在新线程中执行重连任务
                threading.Thread(target=auto_reconnect_task, daemon=True).start()